﻿using Devonline.Communication.Abstractions;
using Devonline.Core;
using Microsoft.Extensions.Logging;
using NModbus;

namespace Devonline.Communication.Modbus;

/// <summary>
/// Modbus 协议基础抽象基类
/// </summary>
public abstract class ModbusCommunicator : Communicator<byte[]>, IModbusCommunicator, IStreamCommunicator
{
    protected readonly IModbusOptions _modbusOptions;
    protected IModbusMaster? _modbusMaster;
    protected ModbusCommunicator(ILogger<Communicator<byte[]>> logger, IModbusOptions options) : base(logger, options)
    {
        _modbusOptions = options;
    }

    /// <summary>
    /// 使能状态
    /// </summary>
    public bool Enable { get; protected set; }

    /// <summary>
    /// 读取离散输入寄存器的值
    /// </summary>
    /// <param name="address">寄存器地址</param>
    /// <returns></returns>
    public virtual async Task<bool> ReadInputAsync(ushort address) => (await ReadInputsAsync(address, AppSettings.UNIT_ONE)).FirstOrDefault();
    /// <summary>
    /// 读取离散输入寄存器的值
    /// </summary>
    /// <param name="address">起始地址</param>
    /// <param name="numberOfPoints">读取数量</param>
    /// <returns></returns>
    public virtual async Task<bool[]> ReadInputsAsync(ushort address, ushort numberOfPoints)
    {
        ArgumentNullException.ThrowIfNull(_modbusMaster);
        var result = await _modbusMaster.ReadInputsAsync(_modbusOptions.SlaveAddress, address, numberOfPoints);
        if (result.Length == numberOfPoints)
        {
            _logger.LogDebug("读取离散输入寄存器起始地址 {address} 中 {numberOfPoints} 个的数据: " + string.Join(AppSettings.DEFAULT_OUTER_SPLITER, result), address.ToString("X"), numberOfPoints);
            return result;
        }

        throw new Exception($"读取离散输入寄存器 {address:X} 失败");
    }

    /// <summary>
    /// 读取线圈寄存器的值
    /// </summary>
    /// <param name="address">寄存器地址</param>
    /// <returns></returns>
    public virtual async Task<bool> ReadCoilAsync(ushort address) => (await ReadCoilsAsync(address, AppSettings.UNIT_ONE)).FirstOrDefault();
    /// <summary>
    /// 读取线圈寄存器的值
    /// </summary>
    /// <param name="address">起始地址</param>
    /// <param name="numberOfPoints">读取数量</param>
    /// <returns></returns>
    public virtual async Task<bool[]> ReadCoilsAsync(ushort address, ushort numberOfPoints)
    {
        ArgumentNullException.ThrowIfNull(_modbusMaster);
        var result = await _modbusMaster.ReadCoilsAsync(_modbusOptions.SlaveAddress, address, numberOfPoints);
        if (result.Length == numberOfPoints)
        {
            _logger.LogDebug("读取线圈寄存器起始地址 {address} 中 {numberOfPoints} 个的数据: " + string.Join(AppSettings.DEFAULT_OUTER_SPLITER, result), address.ToString("X"), numberOfPoints);
            return result;
        }

        throw new Exception($"读取线圈寄存器 {address:X} 失败");
    }

    /// <summary>
    /// 读取输入寄存器的值
    /// </summary>
    /// <param name="address">寄存器地址</param>
    /// <returns></returns>
    public virtual async Task<ushort> ReadInputRegisterAsync(ushort address) => (await ReadInputRegistersAsync(address, AppSettings.UNIT_ONE)).FirstOrDefault();
    /// <summary>
    /// 读取输入寄存器数据
    /// </summary>
    /// <param name="address">起始地址</param>
    /// <param name="numberOfPoints">读取数量</param>
    /// <returns></returns>
    public virtual async Task<ushort[]> ReadInputRegistersAsync(ushort address, ushort numberOfPoints)
    {
        ArgumentNullException.ThrowIfNull(_modbusMaster);
        var result = await _modbusMaster.ReadInputRegistersAsync(_modbusOptions.SlaveAddress, address, numberOfPoints);
        if (result.Length == numberOfPoints)
        {
            _logger.LogDebug("读取输入寄存器起始地址 {address} 中 {numberOfPoints} 个的数据: " + string.Join(AppSettings.DEFAULT_OUTER_SPLITER, result), address.ToString("X"), numberOfPoints);
            return result;
        }

        throw new Exception($"读取输入寄存器 {address:X} 失败");
    }

    /// <summary>
    /// 读取保持寄存器的值
    /// </summary>
    /// <param name="address">寄存器地址</param>
    /// <returns></returns>
    public virtual async Task<ushort> ReadHoldingRegisterAsync(ushort address) => (await ReadHoldingRegistersAsync(address, AppSettings.UNIT_ONE)).FirstOrDefault();
    /// <summary>
    /// 读取保持寄存器的数据
    /// </summary>
    /// <param name="address">起始地址</param>
    /// <param name="numberOfPoints">读取数量</param>
    /// <returns></returns>
    public virtual async Task<ushort[]> ReadHoldingRegistersAsync(ushort address, ushort numberOfPoints)
    {
        ArgumentNullException.ThrowIfNull(_modbusMaster);
        var result = await _modbusMaster.ReadHoldingRegistersAsync(_modbusOptions.SlaveAddress, address, numberOfPoints);
        if (result.Length == numberOfPoints)
        {
            _logger.LogDebug("读取保持寄存器起始地址 {address} 中 {numberOfPoints} 个的数据: " + string.Join(AppSettings.DEFAULT_OUTER_SPLITER, result), address.ToString("X"), numberOfPoints);
            return result;
        }

        throw new Exception($"读取保持寄存器 {address:X} 失败");
    }

    /// <summary>
    /// 写入值到线圈寄存器
    /// </summary>
    /// <param name="address">寄存器地址</param>
    /// <param name="value">值</param>
    /// <returns></returns>
    public virtual async Task WriteCoilAsync(ushort address, bool value)
    {
        _logger.LogDebug("将写入值到线圈寄存器 {address} 中, 值: " + value, address.ToString("X"));
        ArgumentNullException.ThrowIfNull(_modbusMaster);
        await _modbusMaster.WriteSingleCoilAsync(_modbusOptions.SlaveAddress, address, value);
    }
    /// <summary>
    /// 写入数据到线圈寄存器
    /// </summary>
    /// <param name="address">起始地址</param>
    /// <param name="data">值</param>
    /// <returns></returns>
    public virtual async Task WriteCoilsAsync(ushort address, params bool[] data)
    {
        _logger.LogDebug("将写入数据到线圈寄存器 {address} 中, 数据: " + string.Join(AppSettings.DEFAULT_OUTER_SPLITER, data), address.ToString("X"));
        ArgumentNullException.ThrowIfNull(_modbusMaster);
        await _modbusMaster.WriteMultipleCoilsAsync(_modbusOptions.SlaveAddress, address, data);
    }

    /// <summary>
    /// 写入值到保持寄存器
    /// </summary>
    /// <param name="address">寄存器地址</param>
    /// <param name="value">写入的值</param>
    /// <returns></returns>
    public virtual async Task WriteRegisterAsync(ushort address, ushort value)
    {
        _logger.LogDebug("将写入值到保持寄存器 {address} 中, 值: " + value, address.ToString("X"));
        ArgumentNullException.ThrowIfNull(_modbusMaster);
        await _modbusMaster.WriteSingleRegisterAsync(_modbusOptions.SlaveAddress, address, value);
    }
    /// <summary>
    /// 写入数据到保持寄存器
    /// </summary>
    /// <param name="address">起始地址</param>
    /// <param name="data">写入的数据</param>
    /// <returns></returns>
    public virtual async Task WriteRegistersAsync(ushort address, params ushort[] data)
    {
        _logger.LogDebug("将写入数据到保持寄存器 {address} 中, 数据: " + string.Join(AppSettings.DEFAULT_OUTER_SPLITER, data), address.ToString("X"));
        ArgumentNullException.ThrowIfNull(_modbusMaster);
        await _modbusMaster.WriteMultipleRegistersAsync(_modbusOptions.SlaveAddress, address, data);
    }

    /// <summary>
    /// 连续执行某一任务, 直到成功或则到达最大尝试次数后退出
    /// </summary>
    /// <param name="task">需要执行的任务</param>
    /// <returns></returns>
    public virtual async Task<bool> ExecuteAsync(Func<Task<bool>> task)
    {
        var index = 0;
        var result = false;
        while (Enable && index++ < _modbusOptions.MaxRetryCount && !result)
        {
            try
            {
                if (await task())
                {
                    _logger.LogDebug($"第 {index} 次执行获得成功!");
                    result = true;
                    break;
                }
                else
                {
                    _logger.LogDebug($"第 {index} 次执行失败!");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError($"第 {index} 次执行出现错误, " + ex.GetMessage());
                OnError(ex);
            }
            finally
            {
                if (!result)
                {
                    await Task.Delay(_modbusOptions.QueryInterval);
                }
            }
        }

        return result;
    }
}